{#include main fluid=true}
{#style}
#kafka-streams-topology svg {
    height: 100%;
}

#topology-description {
    resize: none;
    font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
}

#kafka-streams-topology .no-outline {
    box-shadow: none !important;
    outline: 0;
}

#kafka-streams-topology .caret {
    border: solid #000;
    border-width: 0 2px 2px 0;
    display: inline;
    cursor: pointer;
    padding: 3px;
    position: absolute;
    right: 0;
    margin-top: 10px;
}

#kafka-streams-topology .list-group-item .collapsed .caret {
    transform: rotate(40deg);
    -webkit-transform: rotate(40deg);
    transition: .3s transform ease-in-out;
}

#kafka-streams-topology .list-group-item .caret {
    transform: rotate(-140deg);
    -webkit-transform: rotate(-140deg);
    transition: .3s transform ease-in-out;
}

#kafka-streams-topology .w-15 {
    width: 15%!important;
}
{/style}
{#scriptref}
<script src="{devRootAppend}/resources/js/mermaid.min.js"></script>
{/scriptref}
{#script}
function toMermaid(topology) {
    var lines = topology.split('\n');
    var subTopologies = [];
    var outside = [];
    var currentGraphNodeName;
    var subTopologiesList = [];
    var topicSourcesList = [];
    var topicSinksList = [];
    var stateStoresList = [];
    var name = (value) => value.replaceAll("-", "-<br>");

    var subTopology = {
        pattern: /Sub-topology: ([0-9]*)/,
        startFormatter: (subTopology) => `subgraph Sub-Topology: $\{subTopology}`,
        endFormatter: () => `end`,
        visit: function(line) {
            var match = line.match(this.pattern);
            // Close the previous sub-topology before opening a new one;
            if(subTopologies.length) {
                subTopologies.push(this.endFormatter());
            }
            subTopologies.push(this.startFormatter(match[1]));
            subTopologiesList.push(match[1]);
        }
    }
    var source = {
        pattern: /Source:\s+(\S+)\s+\(topics:\s+\[(.*)\]\)/,
        formatter: (source, topic) => `$\{topic}[$\{topic}] --> $\{source}($\{name(source)})`,
        visit: function(line) {
            var match = line.match(this.pattern);
                currentGraphNodeName = match[1].trim();
                var topics = match[2]
                topics.split(',').filter(String).map(topic => topic.trim()).forEach(topic => {
                    outside.push(this.formatter(currentGraphNodeName, topic));
                    topicSourcesList.push(topic);
                });
        }
    };

    var processor = {
        pattern: /Processor:\s+(\S+)\s+\(stores:\s+\[(.*)\]\)/,
        formatter: (processor, store) => (processor.includes("JOIN")) ? `$\{store}[($\{name(store)})] --> $\{processor}($\{name(processor)})` : `$\{processor}($\{name(processor)}) --> $\{store}[($\{name(store)})]`,
        visit: function(line) {
            var match = line.match(this.pattern);
                currentGraphNodeName = match[1].trim();
                var stores = match[2];
                stores.split(',').filter(String).map(store => store.trim()).forEach(store => {
                    outside.push(this.formatter(currentGraphNodeName, store));
                    stateStoresList.push(store);
                });
        }
    };

    var sink = {
        pattern: /Sink:\s+(\S+)\s+\(topic:\s+(.*)\)/,
        formatter: (sink, topic) => `$\{sink}($\{name(sink)}) --> $\{topic}[$\{topic}]`,
        visit: function(line) {
            var match = line.match(this.pattern);
            currentGraphNodeName = match[1].trim();
            var topic = match[2].trim();
            outside.push(this.formatter(currentGraphNodeName, topic));
            topicSinksList.push(topic);
        }
    }

    var rightArrow = {
        pattern: /\s*-->\s+(.*)/,
        formatter: (src, dst) => `$\{src}($\{name(src)}) --> $\{dst}($\{name(dst)})`,
        visit: function(line) {
            var match = line.match(this.pattern);
            match[1].split(',').filter(String).map(target => target.trim()).filter(target => target !== "none").forEach(target => {
                subTopologies.push(this.formatter(currentGraphNodeName, target))
            });
        }
    };

    for(const line of lines) {
        switch(true) {
            case subTopology.pattern.test(line):
                subTopology.visit(line);
            break;
            case source.pattern.test(line):
                source.visit(line);
            break;
            case processor.pattern.test(line):
                processor.visit(line);
            break;
            case sink.pattern.test(line):
                sink.visit(line);
            break;
            case rightArrow.pattern.test(line):
                rightArrow.visit(line);
            break;
            default:
            break;
        }

    }

    if(subTopologies.length) {
        subTopologies.push(subTopology.endFormatter());
    }

    var description = ["graph TD"].concat(outside).concat(subTopologies).concat(topicSourcesList).concat(topicSinksList).concat(stateStoresList).join('\n');

    return {
        description: description,
        details: {
            subTopologies: subTopologiesList,
            topicSources: topicSourcesList,
            topicSinks:  topicSinksList,
            stateStores: stateStoresList
        }
    };
}
function svgToCanvas(svgCode) {
    var canvas = document.createElement('canvas');
    try {
        var ctx = canvas.getContext("2d");

        var img = new Image();
        img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgCode)));
        img.onload = () => {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0, img.width, img.height);
        };
    } catch (err) {
        console.error('Failed to generate canvas')
    }
    return canvas;
}
mermaid.initialize(\{startOnLoad:false});
$(function(){

    var canvas = document.createElement("canvas");
    var topologyDescription = $('#topology-description').val();
    var mermaidGraphDefinition = toMermaid(topologyDescription);
    console.log(mermaidGraphDefinition.description);

    var id = "mermaid-graph-" + Date.now();

    mermaid.mermaidAPI.render(id, mermaidGraphDefinition.description, (svgCode, bindFunctions) => {
        canvas = svgToCanvas(svgCode);
        $('#topology-graph-wrapper').html(svgCode);
        $(`#$\{id}`).addClass('mx-auto d-block');   // center the graph
    });

    $('#sub-topologies-details').html(mermaidGraphDefinition.details.subTopologies.length);
    $('#topic-sources-details').text(mermaidGraphDefinition.details.topicSources.length);
    $('#topic-sinks-details').text(mermaidGraphDefinition.details.topicSinks.length);
    $('#state-stores-details').text(mermaidGraphDefinition.details.stateStores.length);

    mermaidGraphDefinition.details.topicSources.sort().forEach(topic => {
        $('#topic-sources-list').append(`<li class="list-group-item">$\{topic}</li>`)
    });

    mermaidGraphDefinition.details.topicSinks.sort().forEach(topic => {
        $('#topic-sinks-list').append(`<li class="list-group-item">$\{topic}</li>`)
    });

    mermaidGraphDefinition.details.stateStores.sort().forEach(store => {
        $('#state-stores-list').append(`<li class="list-group-item">$\{store}</li>`)
    });

    $("#topology-graph-visualization-toggle, #topology-description-visualization-toggle").click(() => {
        $("#topology-description-wrapper").toggleClass("d-none");
        $("#topology-description-visualization-toggle").toggleClass("btn-primary");
        $("#topology-description-visualization-toggle").toggleClass("btn-outline-primary");

        $("#topology-graph-wrapper").toggleClass("d-none");
        $("#topology-graph-visualization-toggle").toggleClass("btn-outline-primary");
        $("#topology-graph-visualization-toggle").toggleClass("btn-primary");
    });

    $("#topology-copy-button").click(() => {
        var isDescriptionVisible = !$("#topology-description-wrapper").hasClass("d-none");
        if (isDescriptionVisible) {
            navigator.clipboard.writeText($('#topology-description').val().trim() + '\n');
        } else {
            canvas.toBlob(blob => {
                navigator.clipboard.write([new ClipboardItem({ ['image/png']: blob })]);
            })
        }
    });

    $("#topology-copy-button").popover({ trigger:"manual" }).click(function() {
        var pop = $(this);
        pop.popover("show")
        pop.on('shown.bs.popover',() => setTimeout(() => pop.popover("hide"), 1000));
    });

    $("#topology-download-button").click(() => {
        var downloadLink = document.createElement('a');

        var isDescriptionVisible = !$("#topology-description-wrapper").hasClass("d-none");
        if (isDescriptionVisible) {
            var description= $('#topology-description').val().trim() + '\n';
            downloadLink.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(description);
            downloadLink.download = 'topology.txt';
        } else {
            downloadLink.href = canvas.toDataURL('image/png');
            downloadLink.download = 'topology.png';
        }

        downloadLink.style.display = 'none';
        document.body.appendChild(downloadLink);

        downloadLink.click();

        document.body.removeChild(downloadLink);
    });
});
{/script}
{#title}Topology{/title}
{#body}
<div id="kafka-streams-topology">
{#if info:topology}
    <div class="row">
        <div class="offset-1">
            <h3>Details</h3>
        </div>
    </div>
    <div class="row mb-3">
        <div class="container list-group list-group-flush">
                    <div class="row list-group-item p-1">
                        <button class="btn no-outline w-15 d-flex justify-content-between align-items-center">
                            Sub-topologies:
                            <span id="sub-topologies-details" class="badge badge-pill badge-secondary">0</span>
                        </button>
                    </div>

                    <div class="row list-group-item p-1">
                        <button class="btn no-outline w-15 d-flex justify-content-between align-items-center collapsed"
                                data-toggle="collapse" href="#collapseTopicSources"
                                aria-expanded="true" aria-controls="collapseTopicSources">
                                Topic sources:
                                <span id="topic-sources-details" class="badge badge-pill badge-secondary">0</span>
                                <span class="caret mr-3"></span>
                            </button>
                        <div class="collapse" id="collapseTopicSources">
                            <div class="card card-body">
                                <ul id="topic-sources-list" class="list-group">
                                </ul>
                            </div>
                        </div>
                    </div>

                    <div class="row list-group-item p-1">
                        <button class="btn no-outline w-15 d-flex justify-content-between align-items-center collapsed"
                        data-toggle="collapse" href="#collapseTopicSinks"
                        aria-expanded="true" aria-controls="collapseTopicSinks">
                            Topic sinks:
                            <span id="topic-sinks-details" class="badge badge-pill badge-secondary">0</span>
                            <span class="caret mr-3"></span>
                    </button>
                        <div class="collapse" id="collapseTopicSinks">
                            <div class="card card-body">
                                <ul id="topic-sinks-list" class="list-group">
                                </ul>
                            </div>
                        </div>
                    </div>

                    <div class="row list-group-item p-1">
                        <button class="btn no-outline w-15 d-flex justify-content-between align-items-center collapsed"
                        data-toggle="collapse" href="#collapseStateStores"
                        aria-expanded="true" aria-controls="collapseStateStores">
                            State stores:
                            <span id="state-stores-details" class="badge badge-pill badge-secondary">2</span>
                            <span class="caret mr-3"></span>
                        </button>
                        <div class="collapse" id="collapseStateStores">
                            <div class="card card-body">
                                <ul id="state-stores-list" class="list-group">
                                </ul>
                            </div>
                        </div>
                    </div>
        </div>
    </div>
    <div class="row">
        <div class="offset-1">
            <h3>Description</h3>
        </div>
    </div>
    <div class="row">
        <div class="container">
            <div class="row mb-3">
                <div class="col">
                    <button type="button" class="btn btn-primary" id="topology-graph-visualization-toggle">
                        <span class="fa fa-project-diagram fa-fw" aria-hidden="true"></span>
                    </button>
                    <button type="button" class="btn btn-outline-primary" id="topology-description-visualization-toggle">
                        <span class="fa fa-align-justify fa-fw" aria-hidden="true"></span>
                    </button>
                </div>
                <div class="col-auto">
                    <button type="button" class="btn btn-secondary" id="topology-copy-button" data-container="body" data-toggle="popover" data-placement="top" data-content="Copied!">
                        <span class="fa fa-copy fa-fw" aria-hidden="true"></span>
                    </button>
                    <button type="button" class="btn btn-success" id="topology-download-button">
                        <span class="fa fa-download fa-fw" aria-hidden="true"></span>
                    </button>
                </div>
            </div>
            <div class="row">
                <div class="container">
                    <div id="topology-description-wrapper" class="d-none">
                        <form>
                            <div class="form-group">
                                <textarea id="topology-description"
                                        readonly=""
                                        class="form-control-plaintext p-3 mb-5 bg-white r border rounded"
                                        rows='{info:topology.describe().toString().split("\r\n|\r|\n").length}'>
    {info:topology.describe().toString()}
                                </textarea>
                            </div>
                        </form>
                    </div>
                    <div id="topology-graph-wrapper">
                    </div>
                </div>
            </div>
        </div>
    </div>
{#else}
{#topologyNotFound /}
{/if}
</div>
{/body}
{/include}
